﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace VKontakteClient
{
    /// <summary>
    /// Перечисление типа отношений с людьми
    /// </summary>
    public enum RelationType { None = 0, Free = 1, HaveAFriend = 2, Affianced = 3, Married = 4, AllBad = 5, Searching = 6 };

    /// <summary>
    /// Перечисление типа пола
    /// </summary>
    public enum Gender { Male, Female, None };

    /// <summary>
    /// Класс описывающий человека
    /// </summary>
    public class Human:INotifyPropertyChanged
    {
        /// <summary>
        /// Словарь со списком полей.
        /// </summary>
        public static Dictionary<string, string> Properties = new Dictionary<string, string>();

        /// <summary>
        /// Статический конструктор заполнения списка полей и их перевода
        /// </summary>
        static Human()
        {
            Properties.Add("ClosedProfile", "закрытость профиля");
            Properties.Add("IsOnline", "в онлайне ли");
            Properties.Add("HighEducation", "высшее образование");
            Properties.Add("LowEducation", "низшее образование");
            Properties.Add("Status", "статус");
            Properties.Add("IsDeleted", "удалена страница");
            Properties.Add("FirstName", "имя");
            Properties.Add("LastName", "фамилию");
            Properties.Add("NickName", "ник");
            Properties.Add("Relation", "отношения");
            Properties.Add("Gender", "пол");
            Properties.Add("BirthDay", "дату рождения");
            Properties.Add("HomeCity", "родной город");
            Properties.Add("Politic", "политические взгляды");
            Properties.Add("Religion", "религию");
            Properties.Add("Country", "страну");
            Properties.Add("City", "город");
            Properties.Add("EMail", "E-Mail");
            Properties.Add("MobilePhone", "мобильный телефон");
            Properties.Add("HomePhone", "домашний телефон");
            Properties.Add("ICQ", "номер аськи");
            Properties.Add("PersonalSite", "личный сайт");
            Properties.Add("ForumNickname", "имя на форуме");
            Properties.Add("Occupy", "занятость");
            Properties.Add("Interests", "интересы");
            Properties.Add("FavoriteMusic", "любимую музыку");
            Properties.Add("FavoriteFilms", "любимые фильмы");
            Properties.Add("FavoriteTV", "любимые передачи");
            Properties.Add("FavoriteBooks", "любимые книги");
            Properties.Add("FavoriteGames", "любимые игры");
            Properties.Add("FavoriteQuotes", "любимые цитаты");
            Properties.Add("About", "информацию о себе");
            Properties.Add("Photo", "аватару");
            Properties.Add("Friends", "друзей");
            Properties.Add("WallMessages", "сообщения на стенке");
            Properties.Add("Audio", "аудиозаписи");
            Properties.Add("Video", "видеозаписи");
        }

        /// <summary>
        /// Локальный кэш параметров
        /// </summary>
        private Dictionary<string, object> _localCache;

        /// <summary>
        /// Шаблонный метод получения последнего элемента в некоем словаре
        /// </summary>
        /// <typeparam name="T">Тип записей в словаре</typeparam>
        /// <param name="d">Словарь</param>
        /// <returns>Последний элемент в словаре</returns>
        private T Last<T>(string PropertyName)
        {
            if (!App.SqlCeAPI.IsPropertyLoaded(this.Id,PropertyName))
            {
                //Type[] q = { typeof(IEnumerable<Human>), typeof(string), typeof(Downloadable) };
                //if (q.Contains(typeof(T)))
                //    this.Refresh(typeof(T)==typeof(IEnumerable<Human>), null);
                //else 
                if (typeof(T) == typeof(IEnumerable<Audio>))
                {
                    if (!App.IsOffline)
                        App.Scheduler.Enqueue(VKontakteAPI.GetUserAudios(this), System.Threading.ThreadPriority.Normal, () => { return "Получение аудиозаписей для " + this.ToString(); }, () => { PropertyChangedCaller("Audio"); });
                }
                else if (typeof(T) == typeof(IEnumerable<Human>))
                {
                    if (!App.IsOffline)
                        App.Scheduler.Enqueue(VKontakteAPI.UpdateUserFriends(this), System.Threading.ThreadPriority.Normal, () => { return "Получение друзей для " + this.ToString(); }, () => { PropertyChangedCaller("Friends"); });
                }
                else return default(T);
            }
            if (_localCache == null) _localCache = new Dictionary<string, object>();
            if (_localCache.Keys.Contains(PropertyName))
            {
                return (T)_localCache[PropertyName];
            }
            T Result = default(T);
            if (typeof(string) == typeof(T))
                Result = (T)(object)App.SqlCeAPI.GetString(this.Id, PropertyName, ShowTime, false);
            else if (typeof(bool) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetBool(this.Id, PropertyName, ShowTime, false);
            else if (typeof(Downloadable) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetDownloadable(this.Id, PropertyName, ShowTime, false);
            else if (typeof(Gender) == typeof(T))
                Result = (T)(Object)(Gender)App.SqlCeAPI.GetInt(this.Id, PropertyName, ShowTime, false);
            else if (typeof(Relation) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetRelation(this.Id, PropertyName, ShowTime, false);
            else if (typeof(DateTime) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetDateTime(this.Id, PropertyName, ShowTime, false);
            else if (typeof(string[]) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetDualStrings(this.Id, PropertyName, ShowTime, false);
            else if (typeof(IEnumerable<Human>) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetFriends(this.Id, PropertyName, ShowTime, false);
            else if (typeof(List<HighEducation>) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetListHighEducation(this.Id, PropertyName, ShowTime, false);
            else if (typeof(IEnumerable<Audio>) == typeof(T))
                Result = (T)(Object)App.SqlCeAPI.GetAudioList(this.Id, PropertyName, ShowTime, false);
            else
                throw new Exception();
            if (typeof(T) != typeof(IEnumerable<Human>))
                _localCache[PropertyName] = Result;
            return Result;
        }

        /// <summary>
        /// Приватное поле даты и времени обновления для использования при обновления значений
        /// </summary>
        public DateTime UpdateTime
        {
            get
            {
                return App.SqlCeAPI.GetUserTime(this.Id, "UpdateTime") ?? DateTime.MinValue;
            }
            set
            {
                App.SqlCeAPI.SetUserTime(value, "UpdateTime", this.Id);
            }
        }

        /// <summary>
        /// Метод установки значения в словарь
        /// </summary>
        /// <typeparam name="T">Тип значения</typeparam>
        /// <param name="Value">Значение для вставки</param>
        /// <param name="PropertyName">Название значения, для уведомления PropertyChanged</param>
        private void Set<T>(T Value, string PropertyName)
        {
            Set(Value, UpdateTime, PropertyName);
        }

        /// <summary>
        /// Метод установки значения в словарь
        /// </summary>
        /// <typeparam name="T">Тип значения</typeparam>
        /// <param name="Value">Значение для вставки</param>
        /// <param name="PropertyName">Название значения, для уведомления PropertyChanged</param>
        /// <param name="When">Дата, используемая при вставке в словарь</param>
        private void Set<T>(T Value, DateTime When, string PropertyName)
        {
            if (Value is string) Value = (T)(object)VKontakteAPI.StripTags((string)(object)Value).Trim();
            T X = default(T);
            if (App.SqlCeAPI.IsPropertyLoaded(this.Id, PropertyName))
                X = Last<T>(PropertyName);

            if (!(Object.Equals(X,default(T)) && Object.Equals(Value,default(T))))
                if (!Object.Equals(X, Value))
                    {
                        if (X is System.Collections.IEnumerable && Value is System.Collections.IEnumerable)
                        {
                            var T1 = X as System.Collections.IEnumerable;
                            var T2 = Value as System.Collections.IEnumerable;
                            List<object> L1 = new List<object>();
                            List<object> L2 = new List<object>();
                            foreach (var v in T1) L1.Add(v);
                            foreach (var v in T2) L2.Add(v);
                            if (L1.Except(L2).Count() == 0 && L2.Except(L1).Count() == 0) return;
                        }
                        if (typeof(T) == typeof(string))
                            App.SqlCeAPI.SetString(this.Id, PropertyName, (string)(object)Value, When);
                        else if (typeof(T) == typeof(bool))
                            App.SqlCeAPI.SetBool(this.Id, PropertyName, (bool)(object)Value, When);
                        else if (typeof(T) == typeof(Downloadable))
                            App.SqlCeAPI.SetDownloadable(this.Id, PropertyName, (Downloadable)(object)Value, When);
                        else if (typeof(T) == typeof(Gender))
                            App.SqlCeAPI.SetInt(this.Id, PropertyName, (int)(object)Value, When);
                        else if (typeof(T) == typeof(Relation))
                            App.SqlCeAPI.SetRelation(this.Id, PropertyName, (Relation)(object)Value, When);
                        else if (typeof(T) == typeof(DateTime))
                            App.SqlCeAPI.SetDateTime(this.Id, PropertyName, (DateTime)(object)Value, When);
                        else if (typeof(string[]) == typeof(T))
                            App.SqlCeAPI.SetDualStrings(this.Id, PropertyName, (string[])(object)Value, When);
                        else if (typeof(IEnumerable<Human>) == typeof(T))
                            App.SqlCeAPI.SetFriends(this.Id, PropertyName, (IEnumerable<Human>)(object)Value, When);
                        else if (typeof(T) == typeof(List<HighEducation>))
                            App.SqlCeAPI.SetListHighEducation(this.Id, PropertyName, (List<HighEducation>)(object)Value, When);
                        else if (typeof(T) == typeof(IEnumerable<Audio>))
                            App.SqlCeAPI.SetAudioList(this.Id, "Audio", (IEnumerable<Audio>)(object)Value, When);
                        else
                            throw new Exception();
                        App.SqlCeAPI.PropertyLoaded(this.Id, PropertyName);
                        if (_localCache == null) _localCache = new Dictionary<string, object>();
                        _localCache[PropertyName] = Value;
                        if (_localCache.Keys.Contains("Modifies")) _localCache.Remove("Modifies");
                        if (PropertyChanged != null)
                        {
                            PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
                            PropertyChanged(this, new PropertyChangedEventArgs("Modifies"));
                        }
                    }
        }

        /// <summary>
        /// Список всех дат модификаций
        /// </summary>
        public IEnumerable<DateTime> Modifies
        {
            get
            {
                if (_localCache == null) _localCache = new Dictionary<string, object>();
                if (_localCache.Keys.Contains("Modifies"))
                    return (IEnumerable<DateTime>)_localCache["Modifies"];
                var X = App.SqlCeAPI.GetModifies(this.Id);
                _localCache["Modifies"] = X;
                return X;
            }
        }

        /// <summary>
        /// Реальная дата текущей версии показываемого профиля.
        /// </summary>
        private DateTime _showTime;

        /// <summary>
        /// Дата текущей версии показываемого профиля.
        /// </summary>
        public DateTime ShowTime
        {
            get
            {
                if (_showTime == DateTime.MinValue)
                {
                    var X = Modifies;
                    if (X.Count() > 0) _showTime = X.Max();
                    else _showTime = DateTime.Now;
                }
                return _showTime;
            }
            set
            {
                _showTime = value;
                _localCache.Clear();
                Rebind();
            }
        }

        /// <summary>
        /// ID человека в ВКонтакте
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Получение свойства по времени
        /// </summary>
        /// <typeparam name="T">Тип свойства</typeparam>
        /// <param name="PropertyName">Имя свойства</param>
        /// <param name="when">Время, из которого надо получить свойство</param>
        /// <param name="IsExact">Было указано точное время</param>
        /// <param name="IsFound">Найдено ли поле</param>
        private object GetByTime<T>(String PropertyName, DateTime when, bool IsExact, ref bool IsFound)
        {
            T X;
            IsFound = false;
            if (typeof(T) == typeof(string))
                X = (T)(object)App.SqlCeAPI.GetString(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(bool))
                X = (T)(object)App.SqlCeAPI.GetBool(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(List<HighEducation>))
                X = (T)(object)App.SqlCeAPI.GetListHighEducation(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(List<LowEducation>))
                X = (T)(object)App.SqlCeAPI.GetListLowEducation(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(Relation))
                X = (T)(object)App.SqlCeAPI.GetRelation(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(Gender))
                X = (T)(object)App.SqlCeAPI.GetInt(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(DateTime))
                X = (T)(object)App.SqlCeAPI.GetDateTime(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(string[]))
                X = (T)(object)App.SqlCeAPI.GetDualStrings(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(Downloadable))
                X = (T)(object)App.SqlCeAPI.GetDownloadable(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(List<Human>))
                X = (T)(object)App.SqlCeAPI.GetFriends(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(List<Audio>))
                X = (T)(object)App.SqlCeAPI.GetAudioList(this.Id, PropertyName, when, IsExact);
            else if (typeof(T) == typeof(List<WallMessage>) || typeof(T) == typeof(List<Video>))
            {
                X = default(T);
#warning не реализовано
            }
            else
                throw new Exception();
            if (!Object.Equals(X, default(T)))
                IsFound = true;
            return X;
        }

        /// <summary>
        /// Получает предыдущее свойство по времени.
        /// </summary>
        /// <param name="PropertyName">Имя свойства</param>
        /// <param name="when">Время когда надо получить</param>
        /// <returns>Возвращает свойство</returns>
        public object GetByTimePrevious(string propertyName, DateTime when, ref bool isFound)
        {
            return GetByTime(propertyName, when - new TimeSpan(1),false, ref isFound);
        }

        /// <summary>
        /// Получает свойство по времени.
        /// </summary>
        /// <param name="PropertyName">Имя свойства</param>
        /// <param name="when">Время когда надо получить</param>
        /// <param name="IsExact">Искать свойство с точным временем?</param>
        /// <param name="IsFound">Найдено ли свойство с таким именем</param>
        /// <returns>Возвращает свойство</returns>
        public object GetByTime(string propertyName, DateTime when, bool isExact, ref bool isFound )
        {
            switch (propertyName)
            {
                case "ClosedProfile": return GetByTime<bool>("ClosedProfile", when,isExact,ref isFound);
                case "IsOnline": return GetByTime<bool>("IsOnline", when, isExact, ref isFound);
                case "IsDeleted": return GetByTime<bool>("IsDeleted", when, isExact, ref isFound);
                case "HighEducation": return GetByTime<List<HighEducation>>("HighEducation", when, isExact, ref isFound);
                case "LowEducation": return GetByTime<List<LowEducation>>("LowEducation", when, isExact, ref isFound);
                case "Status": 
                case "FirstName":
                case "HomeCity": 
                case "Politic": 
                case "Religion": 
                case "Country": 
                case "City": 
                case "EMail": 
                case "MobilePhone": 
                case "HomePhone": 
                case "PersonalSite": 
                case "Occupy": 
                case "Interests": 
                case "FavoriteMusic":
                case "FavoriteFilms":
                case "FavoriteTV": 
                case "FavoriteBooks": 
                case "FavoriteGames": 
                case "FavoriteQuotes": 
                case "About": 
                case "ICQ":
                case "LastName":
                case "NickName": return GetByTime<string>(propertyName, when, isExact, ref isFound);
                case "ForumNickname": return GetByTime<string[]>("ForumNickname", when, isExact, ref isFound);
                case "Relation": return GetByTime<Relation>("Relation", when, isExact, ref isFound);
                case "Gender": return GetByTime<Gender>("Gender", when, isExact, ref isFound);
                case "BirthDay": return GetByTime<DateTime>("BirthDay", when, isExact, ref isFound);
                case "Photo": return GetByTime<Downloadable>("Photo", when, isExact, ref isFound);
                case "Friends": return GetByTime<List<Human>>("Friends", when, isExact, ref isFound);
                case "WallMessages": return GetByTime<List<WallMessage>>("WallMessages", when, isExact, ref isFound);
                case "Audio": return GetByTime<List<Audio>>("Audio", when, isExact, ref isFound);
                case "Video": return GetByTime<List<Video>>("Video", when, isExact, ref isFound);
                default: return null;
            }
        }

        /// <summary>
        /// Посылает PropertyChanged для всех свойств.
        /// </summary>
        public void Rebind()
        {
            foreach (string v in Properties.Keys)
                PropertyChangedCaller(v);
        }

        /// <summary>
        /// Закрытый ли профиль
        /// </summary>
        public bool ClosedProfile
        {
            get { return Last<bool>("ClosedProfile"); }
            set { Set(value, "ClosedProfile"); }
        }

        /// <summary>
        /// В онлайне ли сейчас
        /// </summary>
        public bool IsOnline
        {
            get { return Last<bool>("IsOnline"); }
            set { Set(value, "IsOnline"); }
        }

        /// <summary>
        /// Удалена ли страница
        /// </summary>
        public bool IsDeleted
        {
            get { return Last<bool>("IsDeleted"); }
            set { Set(value, "IsDeleted"); }
        }

        /// <summary>
        /// Список высших образований
        /// </summary>
        public List<HighEducation> HighEducation
        {
            get { return Last<List<HighEducation>>("HighEducation"); }
            set { Set(value, "HighEducation"); }
        }

        /// <summary>
        /// Список начальных образований
        /// </summary>
        public IEnumerable<LowEducation> LowEducation
        {
            get { return Last<IEnumerable<LowEducation>>("LowEducation"); }
            set { Set(value, "LowEducation"); }
        }

        /// <summary>
        /// Статус на сайте
        /// </summary>
        public string Status
        {
            get
            {
                var X = Last<string>("Status");
                if (String.IsNullOrEmpty(X) && this == App.Settings.Myself) X = "[ Изменить статус ]";
                return X;
            }
            set { Set(value, "Status"); }
        }

        /// <summary>
        /// Имя
        /// </summary>
        public string FirstName
        {
            get { return Last<string>("FirstName"); }
            set { Set(value, "FirstName"); }
        }

        /// <summary>
        /// Фамилия
        /// </summary>
        public string LastName
        {
            get { return Last<string>("LastName"); }
            set { Set(value, "LastName"); }
        }

        /// <summary>
        /// Никнейм(или отчество)
        /// </summary>
        public string Nickname
        {

            get { return Last<string>("NickName"); }
            set { Set(value, "NickName"); }
        }

        /// <summary>
        /// Отношения с кем-то
        /// </summary>
        public Relation Relation
        {
            get { return Last<Relation>("Relation"); }
            set { Set(value, "Relation"); }
        }

        /// <summary>
        /// Пол человека
        /// </summary>
        public Gender Gender
        {
            get { return Last<Gender>("Gender"); }
            set { Set(value, "Gender"); }
        }

        /// <summary>
        /// Дата рождения
        /// </summary>
        public DateTime BirthDay

        {
            get { return Last<DateTime>("BirthDay"); }
            set { Set(value, "BirthDay"); }
        }

        /// <summary>
        /// Родной город
        /// </summary>
        public string HomeCity
        {
            get { return Last<string>("HomeCity"); }
            set { Set(value, "HomeCity"); }
        }

        /// <summary>
        /// Политические взгляды
        /// </summary>
        public string Politic
        {
            get { return Last<string>("Politic"); }
            set { Set(value, "Politic"); }
        }

        /// <summary>
        /// Религиозные взгляды
        /// </summary>
        public string Religion
        {
            get { return Last<string>("Religion"); }
            set { Set(value, "Religion"); }
        }

        /// <summary>
        /// Страна
        /// </summary>
        public string Country
        {
            get { return Last<string>("Country"); }
            set { Set(value, "Country"); }
        }

        /// <summary>
        /// Город
        /// </summary>
        public string City
        {
            get { return Last<string>("City"); }
            set { Set(value, "City"); }
        }

        /// <summary>
        /// EMail
        /// </summary>
        public string EMail
        {
            get { return Last<string>("EMail"); }
            set { Set(value, "EMail"); }
        }

        /// <summary>
        /// Номер мобильного телефона
        /// </summary>
        public string MobilePhone
        {
            get { return Last<string>("MobilePhone"); }
            set { Set(value, "MobilePhone"); }
        }

        /// <summary>
        /// Номер домашнего телефона
        /// </summary>
        public string HomePhone
        {
            get { return Last<string>("HomePhone"); }
            set { Set(value, "HomePhone"); }
        }

        /// <summary>
        /// Номер ICQ
        /// </summary>
        public string ICQ
        {
            get { return Last<string>("ICQ"); }
            set { Set(value, "ICQ"); }
        }

        /// <summary>
        /// Адрес личного сайта
        /// </summary>
        public string PersonalSite
        {
            get { return Last<string>("PersonalSite"); }
            set { Set(value, "PersonalSite"); }
        }

        /// <summary>
        /// Никнейм на форуме. Массив из 2х записей. 0 = Ник, 1 = имя форума
        /// </summary>
        public string[] ForumNickname
        {
            get { return Last<string[]>("ForumNickname"); }
            set { Set(value, "ForumNickname"); }
        }

        /// <summary>
        /// Занятость
        /// </summary>
        public string Occupy
        {
            get { return Last<string>("Occupy"); }
            set { Set(value, "Occupy"); }
        }

        /// <summary>
        /// Интересы
        /// </summary>
        public string Interests
        {
            get { return Last<string>("Interests"); }
            set { Set(value, "Interests"); }
        }

        /// <summary>
        /// Любимая музыка
        /// </summary>
        public string FavoriteMusic
        {
            get { return Last<string>("FavoriteMusic"); }
            set { Set(value, "FavoriteMusic"); }
        }

        /// <summary>
        /// Любимые фильмы
        /// </summary>
        public string FavoriteFilms
        {
            get { return Last<string>("FavoriteFilms"); }
            set { Set(value, "FavoriteFilms"); }
        }

        /// <summary>
        /// Любимые ТВ-передачи
        /// </summary>
        public string FavoriteTV
        {
            get { return Last<string>("FavoriteTV"); }
            set { Set(value, "FavoriteTV"); }
        }

        /// <summary>
        /// Любимые книги
        /// </summary>
        public string FavoriteBooks
        {
            get { return Last<string>("FavoriteBooks"); }
            set { Set(value, "FavoriteBooks"); }
        }

        /// <summary>
        /// Любимые игры
        /// </summary>
        public string FavoriteGames
        {
            get { return Last<string>("FavoriteGames"); }
            set { Set(value, "FavoriteGames"); }
        }

        /// <summary>
        /// Любимые цитаты
        /// </summary>
        public string FavoriteQuotes
        {
            get { return Last<string>("FavoriteQuotes"); }
            set { Set(value, "FavoriteQuotes"); }
        }

        /// <summary>
        /// О себе
        /// </summary>
        public string About
        {
            get { return Last<string>("About"); }
            set { Set(value, "About"); }
        }

        /// <summary>
        /// Фото
        /// </summary>
        public Downloadable Photo
        {
            get
            {
                return Last<Downloadable>("Photo");
            }
            set 
            {
                App.SqlCeAPI.PropertyLoaded(this.Id, "Photo");
                int OldLevel = 0;
                var Y = this.Photo;
                if (Y != null)
                {
                    var X = Y.InternetUri;
                    if (X.AbsoluteUri.Contains("/a_") || X.AbsoluteUri.Contains("_a")) OldLevel = 3;
                    if (X.AbsoluteUri.Contains("/b_") || X.AbsoluteUri.Contains("_b")) OldLevel = 2;
                    if (X.AbsoluteUri.Contains("/c_") || X.AbsoluteUri.Contains("_c")) OldLevel = 1;
                }
                int NewLevel = 4;
                if (value.InternetUri.AbsoluteUri.Contains("/a_") || value.InternetUri.AbsoluteUri.Contains("_a")) NewLevel = 3;
                if (value.InternetUri.AbsoluteUri.Contains("/b_") || value.InternetUri.AbsoluteUri.Contains("_b")) NewLevel = 2;
                if (value.InternetUri.AbsoluteUri.Contains("/c_") || value.InternetUri.AbsoluteUri.Contains("_c")) NewLevel = 1;
                if (NewLevel >= OldLevel)
                    Set(value, "Photo");
            }
        }

        /// <summary>
        /// Список друзей
        /// </summary>
        public IEnumerable<Human> Friends
        {
            get { return Last<IEnumerable<Human>>("Friends"); }
            set { Set(value, "Friends"); }
        }

        /// <summary>
        /// Стенка с сообщениями
        /// </summary>
        public IEnumerable<WallMessage> WallMessages
        {
            get { return Last<IEnumerable<WallMessage>>("WallMessages"); }
            set { Set(value, "WallMessages"); }
        }

        /// <summary>
        /// Список аудиозаписей
        /// </summary>
        public IEnumerable<Audio> Audio
        {
            get { return Last<IEnumerable<Audio>>("Audio"); }
            set { Set(value, "Audio"); }
        }

        /// <summary>
        /// Список видеозаписей
        /// </summary>
        public IEnumerable<Video> Video
        {
            get { return Last<IEnumerable<Video>>("Video"); }
            set { Set(value, "Video"); }
        }

        /// <summary>
        /// Загружается ли в данный момент
        /// </summary>
        public bool IsLoading = false;

        /// <summary>
        /// Список измененных свойств.
        /// </summary>
        public List<string> ChangedProperties { get; set; }

        /// <summary>
        /// Метод обновления профиля
        /// </summary>
        public void Refresh(bool isFull, SimpleCallback callback)
        {
            if (!(App.IsOffline || IsLoading))
            {
                IsLoading = true;
                if (ChangedProperties == null) ChangedProperties = new List<string>();
                ChangedProperties.Clear();
                App.Scheduler.Enqueue(VKontakteAPI.UpdateUser(this, isFull), System.Threading.ThreadPriority.Lowest,
                    () => { return "Обновление пользователя №" + Id + "(" + ToString() + ")"; }, callback);
            }
            else
                if (callback != null)
                    callback();
        }

        /// <summary>
        /// Получение пользователя по ID
        /// </summary>
        /// <param name="HumanID">Номер человека</param>
        public static Human GetHuman(int HumanID)
        {
            return GetHuman(HumanID, null);
        }

        /// <summary>
        /// Локлаьный кэш пользователей
        /// </summary>
        private static List<Human> _humansCached = new List<Human>();

        /// <summary>
        /// Получение пользователя по ID
        /// </summary>
        /// <param name="HumanID">ID пользователя</param>
        /// <param name="UpdateUser">Обновить ли сразу же пользователя</param>
        /// <returns>Искомый пользователь</returns>
        public static Human GetHuman(int humanId, bool? updateHuman)
        {
            Human Result;
            lock (_humansCached)
            {
                var X = _humansCached.Where(v => v.Id == humanId);
                if (X.Count() > 0)
                {
                    Result = X.First();
                }
                else
                {
                    if (App.IsOffline) Result = new Human(humanId, false);
                    else if (!updateHuman.HasValue)
                    {
                        if (App.SqlCeAPI.TouchUser(humanId))
                            Result = new Human(humanId, true);
                        else
                            Result = new Human(humanId, false);
                    }
                    else
                        Result = new Human(humanId, updateHuman.Value);
                    _humansCached.Add(Result);
                }
            }
            return Result;
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="HumanID">ID человека</param>
        /// <param name="instantUpdate">Запустить ли сразу обновление юзера</param>
        private Human(int HumanID, bool instantUpdate)
        {
            Id = HumanID;
            if (instantUpdate)
            {
                Loaded = DateTime.MinValue;
                Refresh(false,null);
            }
        }

        /// <summary>
        /// Дата последней загрузки
        /// </summary>
        public DateTime? Loaded
        {
            get
            {
                return App.SqlCeAPI.GetUserTime(this.Id, "Loaded");
            }
            set
            {
                App.SqlCeAPI.SetUserTime(value, "Loaded", this.Id);
            }
        }

        /// <summary>
        /// Перегруженный ToString()
        /// </summary>
        /// <returns>Конкатенированное Имя + Фамилия</returns>
        public override string ToString()
        {
            return String.Join(" ", new string[] { FirstName, Nickname, LastName }.Where(v=>!String.IsNullOrEmpty(v)).ToArray());
        }

        /// <summary>
        /// Метод подергивания события изменения свойства
        /// </summary>
        /// <param name="property">Имя измененного свойства</param>
        public void PropertyChangedCaller(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            if (ChangedProperties != null)
                ChangedProperties.Add(property);
        }

        /// <summary>
        /// Отсылает все изменения из ChangedProperties в Notify
        /// </summary>
        public void ProcessMessages()
        {
            if (OnHumanChanged != null && ChangedProperties.Count()>0)
                OnHumanChanged(new Message() { Human = this, Properties = ChangedProperties.ToArray() });
            ChangedProperties.Clear();
        }

        /// <summary>
        /// Событие изменения свойства
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Делегат, описывающий событие уведомления о смене свойств
        /// </summary>
        /// <param name="message">Сообщение для показа</param>
        public delegate void Notify(Message message);

        /// <summary>
        /// Событие для уведомления о смене свойств человека.
        /// </summary>
        public Notify OnHumanChanged { get; set; }
    }
    
}
